package jscinttools.utilities.aidautilities;

import hep.aida.ref.histogram.Measurement;
import hep.aida.*;
import java.io.*;
import fr.in2p3.ipnl.aidautilities.*;
import java.lang.*;
import jscinttools.utilities.javautilities.MathUtil;

//import java.lang.Object.*;
//import static java.lang.Math.*;


//import aidaCode.*;


/**
 *  Various Static Methods for Scintillation Analysis
 *
 * @author     Marc-Antoine Verdier
 * @created    6 décembre 2010
 */
public final class AUtil implements AidaFactories {
	/**  Constructor for the ScintUtil object  */
	private AUtil() { }


	/**
	 *  Copy an IHistogram1D on a certain range
	 *
	 * @param  theHisto  The IHistogram1D to be copied
	 * @param  range     The range on which to copy
	 * @return           The copied IHistogram1D
	 */
	public static IHistogram1D copyHistogram1D(IHistogram1D theHisto, double[] range) {
		IHistogram1D thePartHisto = AidaFactories.HISTOGRAM_FACTORY.createHistogram1D("thePartHisto", theHisto.axis().bins(), theHisto.axis().lowerEdge(), theHisto.axis().upperEdge());
		for (int i = 0; i < theHisto.axis().bins(); i++)
			if ((theHisto.axis().binCenter(i) >= range[0]) && (theHisto.axis().binCenter(i) <= range[1]))
				thePartHisto.fill(theHisto.axis().binCenter(i), theHisto.binHeight(i));

		//else
		//{
		//	thePartHisto.fill(theHisto.axis().binCenter(i), theHisto.binHeight(i));
		//}

		return thePartHisto;
	}


	/**
	 *  Copy an IHistogram1D on a certain range
	 *
	 * @param  theHisto  The IHistogram1D to be copied
	 * @param  min       The range min
	 * @param  max       The range max
	 * @return           The copied IHistogram1D
	 */
	public static IHistogram1D copyHistogram1D(IHistogram1D theHisto, double min, double max) {
		return copyHistogram1D(theHisto, new double[]{min, max});
	}


	/**
	 *  Normalize an IHistogram1D
	 *
	 * @param  theHisto  The IHistogram1D to normalize
	 * @param  normFac   The normalization factor
	 * @return           The normalized IHistogram1D
	 */
	public static IHistogram1D normHisto(IHistogram1D theHisto, double normFac) {
		theHisto.scale(1.0 / normFac);
		return theHisto;
	}


	/**
	 *  Normalize an IHistogram1D
	 *
	 * @param  theHisto  The IHistogram1D to normalize
	 * @param  range     The range to get the highest bin
	 * @return           The IHistogram1D normalized with its highest bin in range
	 *      = 1
	 */
	public static IHistogram1D normHisto(IHistogram1D theHisto, double[] range) {
		IHistogram1D thePartHisto = copyHistogram1D(theHisto, range);
		return normHisto(theHisto, thePartHisto.maxBinHeight());
	}


	/**
	 *  Normalize an IHistogram1D
	 *
	 * @param  theHisto  The IHistogram1D to normalize
	 * @return           The IHistogram1D normalized with its highest bin = 1
	 */
	public static IHistogram1D normHisto(IHistogram1D theHisto) {
		return normHisto(theHisto, theHisto.maxBinHeight());
	}


	/**
	 *  Description of the Method
	 *
	 * @param  histo  Description of the Parameter
	 * @param  range  Description of the Parameter
	 * @return        Description of the Return Value
	 */
	public static double maxBinHeight(IHistogram1D histo, double[] range) {
		IHistogram1D rangedHisto = copyHistogram1D(histo, range);
		return rangedHisto.maxBinHeight();
	}


	/**
	 *  Fusion two IDataPointSet and sorts it according to increasing x values.
	 *
	 * @param  theDPS1  The first IDataPointSet
	 * @param  theDPS2  The decond IDataPointSet
	 * @return          The fusioned IDatPointSet
	 */
	public static IDataPointSet fusionDPS(IDataPointSet theDPS1, IDataPointSet theDPS2) {
		IDataPointSet fusDPS = AidaFactories.DATA_POINT_SET_FACTORY.create("Fusioned DPS", 2);
		for (int i = 0; i < theDPS1.size(); i++) {
			fusDPS.addPoint();
			fusDPS.point(i).coordinate(0).setValue(theDPS1.point(i).coordinate(0).value());
			fusDPS.point(i).coordinate(0).setErrorMinus(theDPS1.point(i).coordinate(0).errorMinus());
			fusDPS.point(i).coordinate(0).setErrorPlus(theDPS1.point(i).coordinate(0).errorPlus());
			fusDPS.point(i).coordinate(1).setValue(theDPS1.point(i).coordinate(1).value());
			fusDPS.point(i).coordinate(1).setErrorMinus(theDPS1.point(i).coordinate(1).errorMinus());
			fusDPS.point(i).coordinate(1).setErrorPlus(theDPS1.point(i).coordinate(1).errorPlus());
		}
		int interSize = fusDPS.size();
		for (int i = 0; i < theDPS2.size(); i++) {
			fusDPS.addPoint();
			fusDPS.point(interSize + i).coordinate(0).setValue(theDPS2.point(i).coordinate(0).value());
			fusDPS.point(interSize + i).coordinate(0).setErrorMinus(theDPS2.point(i).coordinate(0).errorMinus());
			fusDPS.point(interSize + i).coordinate(0).setErrorPlus(theDPS2.point(i).coordinate(0).errorPlus());
			fusDPS.point(interSize + i).coordinate(1).setValue(theDPS2.point(i).coordinate(1).value());
			fusDPS.point(interSize + i).coordinate(1).setErrorMinus(theDPS2.point(i).coordinate(1).errorMinus());
			fusDPS.point(interSize + i).coordinate(1).setErrorPlus(theDPS2.point(i).coordinate(1).errorPlus());
		}
		fr.in2p3.ipnl.aidautilities.AidaUtil aUtil = new fr.in2p3.ipnl.aidautilities.AidaUtil();
		return aUtil.sortUp(fusDPS);
	}


	/**
	 *  Fill an array of double with log value (eg
	 *  1,2,...,9,10,20,30,...,90,100,200,...)
	 *
	 * @param  min  The min value
	 * @param  max  The max value
	 * @return      The filled double array
	 */
	public static double[] logFill(double min, double max) {
		double initialRange = Math.floor(Math.log10(min));
		double finalRange = Math.floor(Math.log10(max));
		double rangeNumber = finalRange - initialRange;

		double numOfVal = (max / Math.pow(10, finalRange)) - (min / Math.pow(10, initialRange)) + 1 + 9 * rangeNumber;
		double[] array = new double[(int) numOfVal];
		int l = 0;
		for (int j = 0; j < (int) rangeNumber + 1; j++)
			for (int k = 0; k < 9; k++) {
				double logVal = (1.0 + k) * Math.pow(10, initialRange + j);
				if (logVal > min - 1 && logVal < max + 1) {
					array[l] = logVal;
					l++;
				}
			}

		return array;
	}


	/**
	 *  Removes the DataPoint with NaN on Y in an IDataPointSet
	 *
	 * @param  theDPS  The IDataPointSet with NaN
	 * @return         The IDataPointSet without NaN
	 */
	public static IDataPointSet removeNaN(IDataPointSet theDPS) {
		IDataPointSet modDPS = AidaFactories.DATA_POINT_SET_FACTORY.create(theDPS.title(), 2);
		int j = 0;
		for (int i = 0; i < theDPS.size(); i++) {
			//if (theDPS.point(i).coordinate(1).value() != Double.NaN)
			double val = theDPS.point(i).coordinate(1).value();
			if (!Double.isNaN(val)) {
				modDPS.addPoint();
				modDPS.point(j).coordinate(0).setValue(theDPS.point(i).coordinate(0).value());
				modDPS.point(j).coordinate(0).setErrorMinus(theDPS.point(i).coordinate(0).errorMinus());
				modDPS.point(j).coordinate(0).setErrorPlus(theDPS.point(i).coordinate(0).errorPlus());
				modDPS.point(j).coordinate(1).setValue(theDPS.point(i).coordinate(1).value());
				modDPS.point(j).coordinate(1).setErrorMinus(theDPS.point(i).coordinate(1).errorMinus());
				modDPS.point(j).coordinate(1).setErrorPlus(theDPS.point(i).coordinate(1).errorPlus());
				j++;
			}
		}
		return modDPS;
	}

	/**
	 *  Description of the Method
	 *
	 * @param  m1  Description of the Parameter
	 * @param  m2  Description of the Parameter
	 * @return     Description of the Return Value
	 */
	public static IMeasurement divide(IMeasurement m1, IMeasurement m2) {
		return divide(m1.value(), m1.errorPlus(), m2.value(), m2.errorPlus());
	}

	/**
	 *  Description of the Method
	 *
	 * @param  val1  Description of the Parameter
	 * @param  err1  Description of the Parameter
	 * @param  val2  Description of the Parameter
	 * @param  err2  Description of the Parameter
	 * @return       Description of the Return Value
	 */
	public static IMeasurement divide(double val1, double err1, double val2, double err2) {
		double ratio = val1 / val2;
		double ratioErr = MathUtil.uncorrRatioErr(val1, err1, val2, err2);
		return new Measurement(ratio, ratioErr);
	}
	
	public static IMeasurement substract(IMeasurement m1, IMeasurement m2) {
		return substract(m1.value(), m1.errorPlus(), m2.value(), m2.errorPlus());
	}
	
	public static IMeasurement substract(double val1, double err1, double val2, double err2) {
		double sub = val1 - val2;
		double subErr = Math.sqrt(Math.pow(err1, 2) + Math.pow(err2, 2));
		return new Measurement(sub, subErr);
	}

	/**
	 *  Description of the Method
	 *
	 * @param  m1  Description of the Parameter
	 * @param  m2  Description of the Parameter
	 */
	public static void fillMeasurement(IMeasurement m1, IMeasurement m2) {
		fillMeasurement(m1, m2.value(), m2.errorPlus(), m2.errorMinus());
	}

	/**
	 *  Description of the Method
	 *
	 * @param  m1   Description of the Parameter
	 * @param  val  Description of the Parameter
	 * @param  err  Description of the Parameter
	 */
	public static void fillMeasurement(IMeasurement m1, double val, double err) {
		fillMeasurement(m1, val, err, err);
	}

	/**
	 *  Description of the Method
	 *
	 * @param  m1        Description of the Parameter
	 * @param  val       Description of the Parameter
	 * @param  errPlus   Description of the Parameter
	 * @param  errMinus  Description of the Parameter
	 */
	public static void fillMeasurement(IMeasurement m1, double val, double errPlus, double errMinus) {
		m1.setValue(val);
		m1.setErrorPlus(errPlus);
		m1.setErrorMinus(errMinus);
	}
}

